// Includes
#include "SceneManager.h"
#include "ILightSource.h"	// Light source interface header
#include "CAmbientLight.h"	// Ambient light source header

#ifdef _WIN32
#include <windows.h>		// Include windows header on the windows platform
#endif
#include <GL/gl.h>		// OpenGL header
#include <algorithm>	// C++ STL Algorithm header

// Function prototypes
bool	SortSprites(EraAstral::graphics::Sprite *firstSprite, EraAstral::graphics::Sprite *secondSprite);

namespace EraAstral
{
	using namespace graphics;

	namespace core
	{
		// Scene manager constructor
		SceneManager::SceneManager(IDevice *device)
		{
			m_device = device;
			// Create camera
			m_camera = new Camera();
			
			m_useLighting = true;
			
			// Enable ambient light
			glEnable(GL_LIGHT7);
		}
		
		// Scene manager destructor
		SceneManager::~SceneManager()
		{
			// Delete all scene objects
			for (unsigned int i = 0; i < m_objects.size(); i++)
			{
				if (m_objects[i] != NULL)
				{
					delete m_objects[i];
					m_objects[i] = NULL;
				}
			}
			// Clear vector
			m_objects.clear();
			
			// Delete all scene sprites
			for (unsigned int i = 0; i < m_sprites.size(); i++)
			{
				if (m_sprites[i] != NULL)
				{
					delete m_sprites[i];
					m_sprites[i] = NULL;
				}
			}
			// Clear vector
			m_sprites.clear();
			
			// Delete all scene lights
			for (unsigned int i = 0; i < m_lights.size(); i++)
			{
				if (m_lights[i] != NULL)
				{
					delete m_lights[i];
					m_lights[i] = NULL;
				}
			}
			// Clear vector
			m_lights.clear();
		
			// Delete camera
			delete m_camera;
		}
		
		// Adds an object to the scene manager
		void SceneManager::AddObject(GameObject *object)
		{
			// Push object pointer to vector
			m_objects.push_back(object);
		}
		
		// Adds a sprite to the scene manager
		void SceneManager::AddSprite(Sprite *sprite)
		{
			// Push object pointer to vector
			m_sprites.push_back(sprite);
		}
		
		// Adds a light source to the scene manager
		void SceneManager::AddLight(ILightSource *light)
		{
			// Does light already exist?
			for (unsigned int i = 0; i < m_lights.size(); i++)
				if (m_lights[i] == light)
					return;
		
			// Push light pointer to vector
			m_lights.push_back(light);
		}
		
		// Draws all objects held by the scene manager
		void SceneManager::DrawAll()
		{
			// Enable depth testing
			glEnable(GL_DEPTH_TEST);
			// Enable lighting
			if (m_useLighting)
				glEnable(GL_LIGHTING);
				
			// Clear accumulated ambient lighting values
			AmbientLight::ClearAccumAmbient();
				
			// Apply lighting
			for (unsigned int i = 0; i < m_lights.size(); i++)
				if (m_lights[i] != NULL)
					m_lights[i]->Apply();
					
			// Set ambient lighting
			glLightfv(GL_LIGHT7, GL_AMBIENT, AmbientLight::GetAccumAmbient());
			
			// Activate model view matrix
			glMatrixMode(GL_MODELVIEW);
			// Load identity matrix
			glLoadIdentity();
			// Set perspective field of view
			m_device->SetPerspective();	
			
			// Push an OpenGL matrix		
			glPushMatrix();
			
				// Apply camera transforms
				if (m_camera != NULL)
					m_camera->ApplyTransforms();
					
				// Draw all objects
				for (unsigned int i = 0; i < m_objects.size(); i++)
					if (m_objects[i] != NULL)
						m_objects[i]->Draw();
					
			// Pop last OpenGL matrix
			glPopMatrix();					
					
			// Disable all lights
			for (GLenum glLight = GL_LIGHT0; glLight <= GL_LIGHT6; glLight++)
				glDisable(glLight);
			
			// Disable lighting
			glDisable(GL_LIGHTING);
			// Disable depth testing
			glDisable(GL_DEPTH_TEST);
			
			// Push projection matrix
			glMatrixMode(GL_PROJECTION);
			glPushMatrix();
			
			// Load orthographic view
			m_device->SetOrtho();
			
			// Sort sprites by layer
			sort(m_sprites.begin(), m_sprites.end(), SortSprites);
			
			// Draw sprites
			for (unsigned int i = 0; i < m_sprites.size(); i++)
				if (m_sprites[i] != NULL)
					m_sprites[i]->Draw();
					
			// Pop projection matrix
			glMatrixMode(GL_PROJECTION);
			glPopMatrix();
		}
		
		// Gets the camera of the scene
		Camera *SceneManager::GetCamera() const
		{
			return m_camera;
		}
		
		// Gets a specific light from the current scene
		ILightSource *SceneManager::GetLight(unsigned int index) const
		{
			return m_lights[index];
		}
		
		// Gets the number of lights in the current scene
		unsigned int SceneManager::GetLightCount() const
		{
			return m_lights.size();
		}
		
		// Toggles lighting
		void SceneManager::ToggleLighting()
		{
			m_useLighting = !m_useLighting;
		}
	}
}

// Function for comparing two sprite layers - Used for sorting sprites
bool SortSprites(EraAstral::graphics::Sprite *firstSprite, EraAstral::graphics::Sprite *secondSprite)
{
	return firstSprite->GetLayer() < secondSprite->GetLayer();
}
